//+--------------------------------------------------------------------------------
//| Copyright (c) 2012 - 2013 ### an.vuongngocduy
//| This file is a part of Compact Game Engine (CGE)
//+--------------------------------------------------------------------------------
#ifndef __CGE_COLOR_H__
#define __CGE_COLOR_H__

#include "cgeCommons.h"
#include "cgeMath.h"

using namespace cge::std;

namespace cge
{
	namespace gfx
	{
		//! Color class represent RGBA color
		class Color
		{
		public:
			//! HSB format
			union F3HSB
			{
				f32 HSB[3];
				struct { f32 Hue, Saturation, Brightness; };
			};


			//! RGBA format with 4 floats 
			union F4ARGB
			{
				f32 ARGB[4];
				struct { f32 Alpha, Red, Green, Blue; };
			};


		public:
			//! Default constructor
			Color()
				: Red(0), Green(0), Blue(0), Alpha(0)
			{}


			//! Construct new color from specified color components
			Color(u8 r, u8 g, u8 b, u8 a)
				: Red(r), Green(g), Blue(b), Alpha(a)
			{}


			//! Construct new color from integer represent a RGBA color
			Color(u32 argb) 
				: ARGB(argb)
			{}


			//! Destructor
			~Color(){}

			
			//! Set color value
			void Set(u8 r, u8 g, u8 b, u8 a)
			{
				Red = r; Green = g;	Blue = b; Alpha = a;
			}


			//! Set color value
			void Set(u32 argb)
			{ 
				ARGB = argb; 
			}


			//! Test whether color is transparent or not
			b8 IsTransparent() const
			{
				return (Alpha != 0xFF);
			}


			//! Init color from integer represent R8G8B8A8 color
			Color& FromRGBA(u32 rgba)
			{
				Alpha = rgba & 0xFF;
				Blue = (rgba >> 8) & 0xFF;
				Green = (rgba >> 16) & 0xFF;
				Red = (rgba >> 24) & 0xFF;
				return *this;
			}

			
			//! Convert to R8G8B8A8 color
			u32 ToRGBA() const
			{
				return (Alpha | Blue << 8 | Green << 16 | Red << 24);
			}


			//! Init the values of current color from float values
			Color& FromFloats(F4ARGB argb)
			{
				Red = static_cast<u8>(argb.Red * 255.f);
				Green = static_cast<u8>(argb.Green * 255.f);
				Blue = static_cast<u8>(argb.Blue * 255.f);
				Alpha = static_cast<u8>(argb.Alpha * 255.f);
				return *this;
			}


			//! Convert the values of current color to float values
			F4ARGB ToFloats() const
			{
				Color::F4ARGB argb = {0};
				argb.Red = (f32)(Red) / 255.f;
				argb.Green = (f32)(Green) / 255.f;
				argb.Blue = (f32)(Blue) / 255.f;
				argb.Alpha = (f32)(Alpha) / 255.f;
				return argb;
			}


			//! Init color's values from Hue, Saturation and Brightness
			Color& FromHSB(F3HSB hsb)
			{
				Color::F4ARGB clr = {0};

				if (hsb.Brightness == 0.0f)
				{   
					// black
					clr.Red = clr.Green = clr.Blue = 0.0f;
				}
				else if( hsb.Saturation == 0.0f ) 
				{
					// gray
					clr.Red = clr.Green = clr.Blue = hsb.Brightness;
					return *this;
				}
				else
				{
					f32 h1 = hsb.Hue * 6.0f;			// sector 0 to 5
					s32 h2 = (s32)Math::Floor(h1);			
					f32 p = hsb.Brightness * ( 1.0f - hsb.Saturation );
					f32 q = hsb.Brightness * ( 1.0f - hsb.Saturation * (h1 -h2));
					f32 t = hsb.Brightness * ( 1.0f - hsb.Saturation * ( 1 - (h1 - h2)));

					switch( h2 ) 
					{
					case 0:
						clr.Red = hsb.Brightness;
						clr.Green = t;
						clr.Blue = p;
						break;

					case 1:
						clr.Red = q;
						clr.Green = hsb.Brightness;
						clr.Blue = p;
						break;

					case 2:
						clr.Red = p;
						clr.Green = hsb.Brightness;
						clr.Blue = t;
						break;

					case 3:
						clr.Red = p;
						clr.Green = q;
						clr.Blue = hsb.Brightness;
						break;

					case 4:
						clr.Red = t;
						clr.Green = p;
						clr.Blue = hsb.Brightness;
						break;

					default:
						clr.Red = hsb.Brightness;
						clr.Green = p;
						clr.Blue = q;
						break;
					}
				}

				FromFloats(clr);
				return *this;
			}


			//! Convert color's values to Hue, Saturation and Brightness
			F3HSB ToHSB() const
			{
				Color::F3HSB res = {0};
				Color::F4ARGB clr = ToFloats();

				f32 min, max, delta;
				min = Math::Min(3, clr.Red, clr.Green, clr.Blue );
				max = Math::Max(3, clr.Red, clr.Green, clr.Blue );
				res.Brightness = max;					// brightness
				delta = max - min;

				if( max != 0 )
				{
					res.Saturation = delta / max;		// saturation
				}
				else 
				{
					// r = g = b = 0					// saturation = 0, brightness is undefined
					res.Saturation = 0;
					res.Hue = -1;
					return res;
				}

				if( clr.Red == max )
				{
					res.Hue = ( clr.Green - clr.Blue ) / delta;		// between yellow & magenta
				}		
				else if( clr.Green == max )
				{
					res.Hue = 2 + ( clr.Blue - clr.Red ) / delta;	// between cyan & yellow
				}
				else
				{
					res.Hue = 4 + ( clr.Red - clr.Green ) / delta;	// between magenta & cyan
				}

				res.Hue /= 6.0f;									// degrees

				if(res.Hue < 0.0f)
					res.Hue += 1.0f;

				if(res.Hue > 1.0f)
					res.Hue -= 1.0f;

				return res;
			}


			//! Check whether 2 colors are the same
			b8 operator == (const Color& col) const
			{	
				return (Red == col.Red) &&	(Green == col.Green) && (Blue == col.Blue) && (Alpha == col.Alpha);
			}


			//! Check whether 2 colors are different
			b8 operator != (const Color& col) const
			{
				return !(*this == col);
			}


		public:
			union
			{
				struct
				{
					u8 Blue, Green, Red, Alpha;
				};
				u32 ARGB;
			};
		};

	}
}

#endif//__CGE_COLOR_H__